﻿#if UNITY_EDITOR && DEBUG_BEHAVIOUR_TREE
#define DEBUG_TREE
using System;
#endif
using Devil;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace GameToolkit.BehaviourTree
{
    public partial class BehaviourTreePlayer : System.IDisposable, IValidator
    {
        public static void InvokeRecursive(ExecutableEntry root, bool intoSubTree, System.Action<ExecutableEntry> action)
        {
            action(root);
            if (!intoSubTree && root is SubExecutableRoot)
                return;
            if (root is IExecutableParent cnode)
            {
                var size = cnode.ChildCount;
                for (int i = 0; i < size; i++)
                {
                    var child = cnode.GetChild(i);
                    InvokeRecursive(child, intoSubTree, action);
                }
            }
        }

        public static int GetTreeDepth(ExecutableEntry root, bool intoSubTree)
        {
            int depth = 1;
            GetMaxDepth(root, 1, intoSubTree, ref depth);
            return depth;
        }

        static void GetMaxDepth(ExecutableEntry root, int rootDepth, bool intoSubTree, ref int maxDepth)
        {
            if (rootDepth > maxDepth)
                maxDepth = rootDepth;
            if (!intoSubTree && root is SubExecutableRoot)
                return;
            if (root is IExecutableParent cnode)
            {
                var size = cnode.ChildCount;
                for (int i = 0; i < size; i++)
                {
                    var child = cnode.GetChild(i);
                    GetMaxDepth(child, rootDepth + 1, intoSubTree, ref maxDepth);
                }
            }
        }

        readonly object mLock;
        int mUpdateId;
        BehaviourTreePlayer mParent;
        ExecutableEntry mParentEntry;
        IBehaviourTreeExecutable mExecutable;
        BehaviourTreeAsset mAsset;
        BehaviourTreeLooper mMainLooper;
        float mDeltaTime;
        bool mDisposed;
        string mName;
        List<BehaviourTreePlayer> mSubPlayers;
        public string Name => mName;
        public BehaviourTreePlayer Parent => mParent;
        public ExecutableEntry ParentEntry => mParentEntry;
        public BehaviourTreeLooper MainLooper => mMainLooper;
        public NodeState State
        {
            get
            {
                var lp = mMainLooper;
                return lp == null ? NodeState.Inactive : lp.State;
            }
        }
        public int UpdateId => mUpdateId;
        //mMainLooper == null ? NodeState.Inactive : mMainLooper.State;
        public float DeltaTime => mDeltaTime;
        public BehaviourTreeAsset BindingAsset => mAsset;
        public IBehaviourTreeExecutable Executable => mExecutable;
        public bool IsValid => !mDisposed;

        ArrayList mNodeBuffer;

        // root player constructor
        public BehaviourTreePlayer(IBehaviourTreeExecutable executable)
        {
            mLock = new object();
            mName = "ROOT";
            mExecutable = executable;
#if DEBUG_TREE
            _sync_binding_lock = new object();
            mUpdatingNodes = new HashSet<object>();
            mUpdatingStates = new HashSet<IStateValidator>();
            mSubmitPlayers = new HashSet<BehaviourTreePlayer>();
            if (ParallelUnity.IsMainThread)
                NotifyCreate();
            else
                MainThread.RunOnMainThread(NotifyCreate);
#endif
        }

        // sub player constructor
        private BehaviourTreePlayer(BehaviourTreePlayer parent, ExecutableEntry parentEntry, string name)
        {
            mParent = parent;
            mParentEntry = parentEntry;
            mLock = parent.mLock;
            mExecutable = parent.mExecutable;
            mName = string.IsNullOrEmpty(name) ? "SUB" : name;
#if DEBUG_TREE
            _sync_binding_lock = parent._sync_binding_lock;
            mUpdatingNodes = parent.mUpdatingNodes;
            mUpdatingStates = parent.mUpdatingStates;
            mSubmitPlayers = parent.mSubmitPlayers;
#endif
        }

        public BehaviourTreePlayer GetRootPlayer()
        {
            lock (mLock)
            {
                var player = this;
                while (player.mParent != null)
                {
                    player = player.mParent;
                }
                return player;
            }
        }

        public bool IsChildOf(BehaviourTreePlayer player, bool considerSelf)
        {
            lock (mLock)
            {
                var p = considerSelf ? this : mParent;
                while (p != null)
                {
                    if (p == player)
                        return true;
                    p = p.mParent;
                }
            }
            return false;
        }

        public BehaviourTreePlayer CreateSubTreePlayer(ExecutableEntry entry, string playerName = null)
        {
            BehaviourTreePlayer p;
            lock (mLock)
            {
                p = new BehaviourTreePlayer(this, entry, playerName);
                if (mSubPlayers == null)
                    mSubPlayers = new List<BehaviourTreePlayer>();
                mSubPlayers.Add(p);
            }
#if DEBUG_TREE
            var player = this;
            while (player != null)
            {
                p.mSubPlayerChanged?.Invoke(p);
                player = player.mParent;
            }
#endif
            return p;
        }

        public BehaviourTreeLooper CreateLooper(ExecutableEntry root)
        {
            return new BehaviourTreeLooper(this, root, false);
        }

        public BehaviourTreePlayer FindPlayerForAsset(BehaviourTreeAsset asset)
        {
            lock (mLock)
            {
                return GetPlayerRecursive(asset);
            }
        }

        // 递归获取已加载行为树
        BehaviourTreePlayer GetPlayerRecursive(BehaviourTreeAsset asset)
        {
            if (mAsset == asset)
                return this;
            var subs = mSubPlayers;
            if (subs != null)
            {
                for (int i = subs.Count - 1; i >= 0; i--)
                {
                    var p = subs[i]?.GetPlayerRecursive(asset);
                    if (p != null)
                        return p;
                }
            }
            return null;
        }

        public void SetBehaviourTree(BehaviourTreeAsset asset)
        {
            lock (mLock)
            {
                if (!mDisposed && asset != mAsset)
                {
                    // 检查父行为树中是否已经加载过资源，防止死锁
                    var p = asset == null ? null : mParent;
                    while (p != null)
                    {
                        if (p.mAsset == asset)
                        {
                            throw new System.Exception($"The behaviour tree asset (\"{asset.Title}\") was binding in parent player.");
                        }
                        p = p.mParent;
                    }
                    var looper = mMainLooper;
                    mMainLooper = null;
                    mAsset = asset;

                    if (looper != null)
                    {
                        try
                        {
                            looper.Interrupt(false, false);
                            looper.Root.InvokeDestroyRecursive(this);
                        }
                        catch (System.Exception e)
                        {
                            Debug.LogException(e);
                        }
                    }

                    if (asset != null)
                    {
                        try
                        {
                            var root = new CreateBehaviourTreeContext(this).Create(asset);
                            mMainLooper = new BehaviourTreeLooper(this, root, true);
                            root.InvokeCreateRecursive(this);
                        }
                        catch (System.Exception e)
                        {
                            Debug.LogException(e);
                            mMainLooper = null;
                        }
                    }
#if DEBUG_TREE
                    ResetGuidMap();
#endif
                }
            }
        }

        public void Dispose()
        {
            lock (mLock)
            {
                if (mDisposed)
                    return;
                mDisposed = true;
            }
            OnDisposing();
        }

        void OnDisposing()
        {
            var looper = mMainLooper;
            var sub = mSubPlayers;
            var parent = mParent;
            mParent = null;
            mAsset = null;
            if (parent != null)
            {
                lock (parent.mLock)
                {
                    parent.mSubPlayers?.Remove(this);
                }
#if DEBUG_TREE
                while (parent != null && !parent.mDisposed)
                {
                    parent.mSubPlayerChanged?.Invoke(parent);
                    parent = parent.Parent;
                }
#endif
            }
            if (sub != null)
            {
                for (int i = 0; i < sub.Count; i++)
                {
                    sub[i]?.Dispose();
                }
            }
            if (looper != null)
            {
                try
                {
                    looper.Interrupt(false, false);
                    looper.Root.InvokeDestroyRecursive(this);
                }
                catch (System.Exception e)
                {
                    Debug.LogException(e);
                }
            }
            mMainLooper = null;
            mSubPlayers = null;
            mExecutable = null;
#if DEBUG_TREE
            if (ParallelUtils.IsMainThread)
                NotifyDispose();
            else
                MainThread.RunOnMainThread(NotifyDispose);
#endif
        }

        /// <summary>
        /// 获取正在执行的节点类型
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="intoSubTree"></param>
        /// <returns></returns>
        public T GetCurrentBehaviour<T>(bool intoSubTree) where T : class
        {
            lock (mLock)
            {
                var looper = mMainLooper;
                if (looper == null)
                    return default;
                if (mNodeBuffer == null)
                    mNodeBuffer = new ArrayList();
                else
                    mNodeBuffer.Clear();
                mNodeBuffer.Add(looper);
                var index = 0;
                while (index < mNodeBuffer.Count)
                {
                    looper = (BehaviourTreeLooper)mNodeBuffer[index++];
                    var node = looper.GetActiveNode<T>();
                    if (node != null)
                    {
                        mNodeBuffer.Clear();
                        return node;
                    }
                    var activeNode = looper.ActiveNode;
                    if (activeNode is IBehaviourTreeContext context)
                    {
                        for (int i = 0; i < context.LoopersCount; i++)
                        {
                            var next = context.GetLooper(i);
                            if (next != null && !mNodeBuffer.Contains(next))
                                mNodeBuffer.Add(next);
                        }
                    }
                    if (intoSubTree && activeNode is IBehaviourTreeBinder subTree)
                    {
                        var player = subTree.GetPlayerForAsset(subTree.behaviourTreeAsset);
                        var next = player == null ? null : player.mMainLooper;
                        if (next != null && !mNodeBuffer.Contains(next))
                            mNodeBuffer.Add(next);
                    }
                }
                return null;
            }
        }

        public T FindBehaviour<T>(bool intoSubTree, FilterDelegate<T> filter) where T: class
        {
            lock (mLock)
            {
                var looper = mMainLooper;
                object node = looper == null ? null : looper.Root;
                if (node == null)
                    return default;

                if (mNodeBuffer == null)
                    mNodeBuffer = new ArrayList();
                else
                    mNodeBuffer.Clear();
                mNodeBuffer.Add(node);
                var index = 0;
                while (index < mNodeBuffer.Count)
                {
                    node = mNodeBuffer[index++];
                    if (node is T t && (filter == null || filter(t)))
                        return t;
                    if (node is IExecutableParent parent)
                    {
                        for (int i = 0; i < parent.ChildCount; i++)
                        {
                            mNodeBuffer.Add(parent.GetChild(i));
                        }
                    }
                    if(intoSubTree && node is IBehaviourTreeBinder subTree)
                    {
                        var player = subTree.GetPlayerForAsset(subTree.behaviourTreeAsset);
                        var next = player == null ? null : player.mMainLooper;
                        var subNode = next == null ? null : next.Root;
                        if (subNode != null && !mNodeBuffer.Contains(subNode))
                            mNodeBuffer.Add(subNode);
                    }
                }
                return null;
            }
        }

        public void UpdateBehaviour(float deltaTime)
        {
            mUpdateId++;
#if DEBUG_TREE
            if (mParent == null)
            {
                lock (_sync_binding_lock)
                {
                    mUpdatingNodes.Clear();
                }
            }
#endif
            mDeltaTime = deltaTime;
            lock (mLock)
            {
                var looper = mMainLooper;
                if (looper != null)
                {
                    looper.UpdateBehaviour();
                }
            }
#if DEBUG_TREE
            if (mParent == null && _is_binding)
            {
                lock (_sync_binding_lock)
                {
                    if (ParallelUtils.IsMainThread)
                        SubmitBehaviourStates();
                    else
                        MainThread.RunOnMainThread(SubmitBehaviourStates);
                }
            }
#endif
        }

        ~BehaviourTreePlayer()
        {
            Dispose();
        }

        public override string ToString()
        {
            if (mAsset == null)
                return $"<none>";
            else if (mParent == null)
                return $"ROOT: {mAsset.Title}";
            else
                return $"SUB: {mAsset.Title}";
        }

        

    }

}
